﻿using System;
using System.Collections;
using System.Collections.Generic;
using GeoAPI.Geometries;
using NetTopologySuite.Algorithm;
using NetTopologySuite.GeometriesGraph;
using NetTopologySuite.Utilities;

namespace NetTopologySuite.Operation.Valid
{
	public class IsValidOp
	{
		public static bool IsValidCoordinate(Coordinate coord)
		{
			return !double.IsNaN(coord.X) && !double.IsInfinity(coord.X) && !double.IsNaN(coord.Y) && !double.IsInfinity(coord.Y);
		}

		public static Coordinate FindPointNotNode(Coordinate[] testCoords, ILinearRing searchRing, GeometryGraph graph)
		{
			EdgeIntersectionList edgeIntersectionList = graph.FindEdge(searchRing).EdgeIntersectionList;
			foreach (Coordinate coordinate in testCoords)
			{
				if (!edgeIntersectionList.IsIntersection(coordinate))
				{
					return coordinate;
				}
			}
			return null;
		}

		public IsValidOp(IGeometry parentGeometry)
		{
			this.parentGeometry = parentGeometry;
		}

		public bool IsSelfTouchingRingFormingHoleValid { get; set; }

		public bool IsValid
		{
			get
			{
				this.CheckValid(this.parentGeometry);
				return this.validErr == null;
			}
		}

		public TopologyValidationError ValidationError
		{
			get
			{
				this.CheckValid(this.parentGeometry);
				return this.validErr;
			}
		}

		private void CheckValid(IGeometry g)
		{
			if (this.isChecked)
			{
				return;
			}
			this.validErr = null;
			if (g.IsEmpty)
			{
				return;
			}
			if (g is IPoint)
			{
				this.CheckValid((IPoint)g);
				return;
			}
			if (g is IMultiPoint)
			{
				this.CheckValid((IMultiPoint)g);
				return;
			}
			if (g is ILinearRing)
			{
				this.CheckValid((ILinearRing)g);
				return;
			}
			if (g is ILineString)
			{
				this.CheckValid((ILineString)g);
				return;
			}
			if (g is IPolygon)
			{
				this.CheckValid((IPolygon)g);
				return;
			}
			if (g is IMultiPolygon)
			{
				this.CheckValid((IMultiPolygon)g);
				return;
			}
			if (g is IGeometryCollection)
			{
				this.CheckValid((IGeometryCollection)g);
				return;
			}
			throw new NotSupportedException(g.GetType().FullName);
		}

		private void CheckValid(IPoint g)
		{
			this.CheckInvalidCoordinates(g.Coordinates);
		}

		private void CheckValid(IMultiPoint g)
		{
			this.CheckInvalidCoordinates(g.Coordinates);
		}

		private void CheckValid(ILineString g)
		{
			this.CheckInvalidCoordinates(g.Coordinates);
			if (this.validErr != null)
			{
				return;
			}
			GeometryGraph graph = new GeometryGraph(0, g);
			this.CheckTooFewPoints(graph);
		}

		private void CheckValid(ILinearRing g)
		{
			this.CheckInvalidCoordinates(g.Coordinates);
			if (this.validErr != null)
			{
				return;
			}
			this.CheckClosedRing(g);
			if (this.validErr != null)
			{
				return;
			}
			GeometryGraph geometryGraph = new GeometryGraph(0, g);
			this.CheckTooFewPoints(geometryGraph);
			if (this.validErr != null)
			{
				return;
			}
			LineIntersector li = new RobustLineIntersector();
			geometryGraph.ComputeSelfNodes(li, true);
			this.CheckNoSelfIntersectingRings(geometryGraph);
		}

		private void CheckValid(IPolygon g)
		{
			this.CheckInvalidCoordinates(g);
			if (this.validErr != null)
			{
				return;
			}
			this.CheckClosedRings(g);
			if (this.validErr != null)
			{
				return;
			}
			GeometryGraph graph = new GeometryGraph(0, g);
			this.CheckTooFewPoints(graph);
			if (this.validErr != null)
			{
				return;
			}
			this.CheckConsistentArea(graph);
			if (this.validErr != null)
			{
				return;
			}
			if (!this.IsSelfTouchingRingFormingHoleValid)
			{
				this.CheckNoSelfIntersectingRings(graph);
				if (this.validErr != null)
				{
					return;
				}
			}
			this.CheckHolesInShell(g, graph);
			if (this.validErr != null)
			{
				return;
			}
			this.CheckHolesNotNested(g, graph);
			if (this.validErr != null)
			{
				return;
			}
			this.CheckConnectedInteriors(graph);
		}

		private void CheckValid(IMultiPolygon g)
		{
			foreach (IPolygon poly in g.Geometries)
			{
				this.CheckInvalidCoordinates(poly);
				if (this.validErr != null)
				{
					return;
				}
				this.CheckClosedRings(poly);
				if (this.validErr != null)
				{
					return;
				}
			}
			GeometryGraph graph = new GeometryGraph(0, g);
			this.CheckTooFewPoints(graph);
			if (this.validErr != null)
			{
				return;
			}
			this.CheckConsistentArea(graph);
			if (this.validErr != null)
			{
				return;
			}
			if (!this.IsSelfTouchingRingFormingHoleValid)
			{
				this.CheckNoSelfIntersectingRings(graph);
				if (this.validErr != null)
				{
					return;
				}
			}
			foreach (IPolygon p in g.Geometries)
			{
				this.CheckHolesInShell(p, graph);
				if (this.validErr != null)
				{
					return;
				}
			}
			foreach (IPolygon p2 in g.Geometries)
			{
				this.CheckHolesNotNested(p2, graph);
				if (this.validErr != null)
				{
					return;
				}
			}
			this.CheckShellsNotNested(g, graph);
			if (this.validErr != null)
			{
				return;
			}
			this.CheckConnectedInteriors(graph);
		}

		private void CheckValid(IGeometryCollection gc)
		{
			foreach (IGeometry g in gc.Geometries)
			{
				this.CheckValid(g);
				if (this.validErr != null)
				{
					return;
				}
			}
		}

		private void CheckInvalidCoordinates(Coordinate[] coords)
		{
			foreach (Coordinate coordinate in coords)
			{
				if (!IsValidOp.IsValidCoordinate(coordinate))
				{
					this.validErr = new TopologyValidationError(TopologyValidationErrors.InvalidCoordinate, coordinate);
					return;
				}
			}
		}

		private void CheckInvalidCoordinates(IPolygon poly)
		{
			this.CheckInvalidCoordinates(poly.ExteriorRing.Coordinates);
			if (this.validErr != null)
			{
				return;
			}
			foreach (ILineString lineString in poly.InteriorRings)
			{
				this.CheckInvalidCoordinates(lineString.Coordinates);
				if (this.validErr != null)
				{
					return;
				}
			}
		}

		private void CheckClosedRings(IPolygon poly)
		{
			this.CheckClosedRing(poly.Shell);
			if (this.validErr != null)
			{
				return;
			}
			foreach (ILinearRing ring in poly.Holes)
			{
				this.CheckClosedRing(ring);
				if (this.validErr != null)
				{
					return;
				}
			}
		}

		private void CheckClosedRing(ILinearRing ring)
		{
			if (!ring.IsClosed)
			{
				this.validErr = new TopologyValidationError(TopologyValidationErrors.RingNotClosed, ring.GetCoordinateN(0));
			}
		}

		private void CheckTooFewPoints(GeometryGraph graph)
		{
			if (graph.HasTooFewPoints)
			{
				this.validErr = new TopologyValidationError(TopologyValidationErrors.TooFewPoints, graph.InvalidPoint);
				return;
			}
		}

		private void CheckConsistentArea(GeometryGraph graph)
		{
			ConsistentAreaTester consistentAreaTester = new ConsistentAreaTester(graph);
			if (!consistentAreaTester.IsNodeConsistentArea)
			{
				this.validErr = new TopologyValidationError(TopologyValidationErrors.SelfIntersection, consistentAreaTester.InvalidPoint);
				return;
			}
			if (consistentAreaTester.HasDuplicateRings)
			{
				this.validErr = new TopologyValidationError(TopologyValidationErrors.DuplicateRings, consistentAreaTester.InvalidPoint);
				return;
			}
		}

		private void CheckNoSelfIntersectingRings(GeometryGraph graph)
		{
			IEnumerator edgeEnumerator = graph.GetEdgeEnumerator();
			while (edgeEnumerator.MoveNext())
			{
				object obj = edgeEnumerator.Current;
				Edge edge = (Edge)obj;
				this.CheckNoSelfIntersectingRing(edge.EdgeIntersectionList);
				if (this.validErr != null)
				{
					return;
				}
			}
		}

		private void CheckNoSelfIntersectingRing(EdgeIntersectionList eiList)
		{
			HashSet<Coordinate> hashSet = new HashSet<Coordinate>();
			bool flag = true;
			foreach (EdgeIntersection edgeIntersection in eiList)
			{
				if (flag)
				{
					flag = false;
				}
				else
				{
					if (hashSet.Contains(edgeIntersection.Coordinate))
					{
						this.validErr = new TopologyValidationError(TopologyValidationErrors.RingSelfIntersection, edgeIntersection.Coordinate);
						break;
					}
					hashSet.Add(edgeIntersection.Coordinate);
				}
			}
		}

		private void CheckHolesInShell(IPolygon p, GeometryGraph graph)
		{
			ILinearRing shell = p.Shell;
			IPointInRing pointInRing = new MCPointInRing(shell);
			for (int i = 0; i < p.NumInteriorRings; i++)
			{
				Coordinate coordinate = IsValidOp.FindPointNotNode(p.Holes[i].Coordinates, shell, graph);
				if (coordinate == null)
				{
					return;
				}
				if (!pointInRing.IsInside(coordinate))
				{
					this.validErr = new TopologyValidationError(TopologyValidationErrors.HoleOutsideShell, coordinate);
					return;
				}
			}
		}

		private void CheckHolesNotNested(IPolygon p, GeometryGraph graph)
		{
			IndexedNestedRingTester indexedNestedRingTester = new IndexedNestedRingTester(graph);
			foreach (ILinearRing ring in p.Holes)
			{
				indexedNestedRingTester.Add(ring);
			}
			if (!indexedNestedRingTester.IsNonNested())
			{
				this.validErr = new TopologyValidationError(TopologyValidationErrors.NestedHoles, indexedNestedRingTester.NestedPoint);
			}
		}

		private void CheckShellsNotNested(IMultiPolygon mp, GeometryGraph graph)
		{
			for (int i = 0; i < mp.NumGeometries; i++)
			{
				ILinearRing shell = ((IPolygon)mp.GetGeometryN(i)).Shell;
				for (int j = 0; j < mp.NumGeometries; j++)
				{
					if (i != j)
					{
						IPolygon p = (IPolygon)mp.GetGeometryN(j);
						this.CheckShellNotNested(shell, p, graph);
						if (this.validErr != null)
						{
							return;
						}
					}
				}
			}
		}

		private void CheckShellNotNested(ILinearRing shell, IPolygon p, GeometryGraph graph)
		{
			Coordinate[] coordinates = shell.Coordinates;
			ILinearRing shell2 = p.Shell;
			Coordinate[] coordinates2 = shell2.Coordinates;
			Coordinate coordinate = IsValidOp.FindPointNotNode(coordinates, shell2, graph);
			if (coordinate == null)
			{
				return;
			}
			if (!CGAlgorithms.IsPointInRing(coordinate, coordinates2))
			{
				return;
			}
			if (p.NumInteriorRings <= 0)
			{
				this.validErr = new TopologyValidationError(TopologyValidationErrors.NestedShells, coordinate);
				return;
			}
			Coordinate coordinate2 = null;
			for (int i = 0; i < p.NumInteriorRings; i++)
			{
				ILinearRing hole = p.Holes[i];
				coordinate2 = this.CheckShellInsideHole(shell, hole, graph);
				if (coordinate2 == null)
				{
					return;
				}
			}
			this.validErr = new TopologyValidationError(TopologyValidationErrors.NestedShells, coordinate2);
		}

		private Coordinate CheckShellInsideHole(ILinearRing shell, ILinearRing hole, GeometryGraph graph)
		{
			Coordinate[] coordinates = shell.Coordinates;
			Coordinate[] coordinates2 = hole.Coordinates;
			Coordinate coordinate = IsValidOp.FindPointNotNode(coordinates, hole, graph);
			if (coordinate != null && !CGAlgorithms.IsPointInRing(coordinate, coordinates2))
			{
				return coordinate;
			}
			Coordinate coordinate2 = IsValidOp.FindPointNotNode(coordinates2, shell, graph);
			if (coordinate2 == null)
			{
				Assert.ShouldNeverReachHere("points in shell and hole appear to be equal");
				return null;
			}
			if (CGAlgorithms.IsPointInRing(coordinate2, coordinates))
			{
				return coordinate2;
			}
			return null;
		}

		private void CheckConnectedInteriors(GeometryGraph graph)
		{
			ConnectedInteriorTester connectedInteriorTester = new ConnectedInteriorTester(graph);
			if (!connectedInteriorTester.IsInteriorsConnected())
			{
				this.validErr = new TopologyValidationError(TopologyValidationErrors.DisconnectedInteriors, connectedInteriorTester.Coordinate);
			}
		}

		private readonly IGeometry parentGeometry;

		private bool isChecked;

		private TopologyValidationError validErr;
	}
}
